# MemoryManager implement a logical layer on physical memory
# Strategy can be: continuousAssignment / Pagination
class MemoryManager:
    def __init__(self,filesystem,strategyManager):
        self.filesystem = filesystem
        self.strategyManager = strategyManager
    
    def getStrategyManager(self):
        return self.strategyManager
    
    #load program in memory it depends on strategy
    def load(self, path):
        self.getStrategyManager().load(self.filesystem,path)


# inherited from ManagerStrategy
class ContinuousAssignment:
    def __init__(self,implementation,memory):
        self.emptyBlocks = []
        self.usedBlocks = []
        self.blocks = []
        self.implementation= implementation
        #initialized the first empty block
        self.memory = memory
        self.emptyBlocks.append(Block(0,memory.size()))
        
    def load(self,fileSystem, path):
        file = fileSystem.getFile(path)
        totalSize = file.getSize()
        emptyBlock = self.getImplementation.getEmptyBlock(self.emptyBlocks,totalSize)
        self.saveBlock(emptyBlock,file)
    
    def saveBlock(self, emptyBlock,file):
        position = emptyBlock.getStartPostion()
        for block in file.getBlocks():
            for instruction in block.getInstructions():
                self.memory.set(position,instruction)
                position = position + 1
        #the final instruction is the end position of empty block 
        emptyBlock.setEndPosition(position)
        #create a free block with free empty block size
        freeBlockStartPosition = position + 1 
        freeBlockEndPosition = emptyBlock.getEndPosition()
        #turn empty block to used one
        self.usedBlocks.append(emptyBlock)
        #create a new empty Block
        newEmptyBlock = Block(freeBlockStartPosition,freeBlockEndPosition,emptyBlock.getBlockID()+1)
        if (newEmptyBlock.size() > 0):
            self.blocks.append(newEmptyBlock)
            self.emptyBlocks.append(newEmptyBlock)

    def getData(self,block):
        result = []
        position = block.startPosition
        while (position == block.endPosition):
                result.append(self.memory.get(position))
                position = position + 1 
        return result  
    
    def compact(self):
        startPosition = 0
        for block in self.emptyBlocks():
            startPosition = self.moveBlock(block,startPosition)
        #TODO create a BlockID Generator
        emptyBlock = Block(startPosition,len(self.blocks),2)
        self.emptyBlocks = []
        self.emptyBlocks.append(emptyBlock)
    
    def moveBlock(self,block,position):
        originalBlockStartPosition = block.getStartPosition()
        movedBlockStartPosition = position
        blockSize = block.getSize()
        for pos in range(blockSize):
            instruction = self.memory.get(originalBlockStartPosition)
            self.memory.set(movedBlockStartPosition,instruction)
            originalBlockStartPosition = originalBlockStartPosition + pos
            movedBlockStartPosition = movedBlockStartPosition + pos 
        return movedBlockStartPosition + pos

class Block:
    def __init__(self,startPosition,endPosition, blockID):
        self.blockID = blockID
        self.startPosition = startPosition
        self.endPosition = endPosition
      
    def getSize(self):
        return self.endPosition - self.startPosition
    
    def getStartPosition(self): 
        return self.startPosition
    
    def setStartPosition(self,position): 
        self.startPosition = position
    
    def getEndPosition(self):
        return self.endPosition
    
    def setEndPosition(self,position):
        self.endPosition = position
     
    def getBlockID(self):
        return self.blockID
    
    def setBlockID(self,blockid):
        self.blockID = blockid

    
class FileSystem:
    def __init__(self,hardisk):
        #create a tree with root :)
        self.hardisk = hardisk
        self.relativePosition = self.tree
        self.tree = Folder('root')
        
    #Load the fileSystem structure from hardisk
    def load(self, hardisk):
        #Sector 0 must be root folder
        rootFolder = self.hardisk.get(0)
        self.relativePosition = rootFolder
        self.createChildren(self,rootFolder,rootFolder)
    
    #parse file system positions returning a list of positions
    def parseFilesystemPosition(self,stringPositions):
        return stringPositions.split(",")
    
    def createChildren(self,filesystemParent):
        filesystemParent.md(self)
        fsPositions = self.parseFilesystemPosition(self.getFilesystemPosition())
        for fsPosition in fsPositions:
            fileSystemElement = self.hardisk.get(fsPosition)
            self.createChildren(fileSystemElement,self)
            
class Folder:
    def __init__(self,folderName,folderParent = None):
        self.folderName = folderName
        self.fileSystem = []
        self.folderParent = folderParent
    
    def md(self,folderName):
        folder = Folder(folderName,self)
        self.fileSystem.append(folder)
        
    #Return the instructions size (cells)
    def getSize(self):
        size = 0
        for fileSystem in self.fileSystem():
            size = size + fileSystem.getSize()
        return size
    
class File:
    def __init__(self, name):
        self.blocks = []
        self.name = name
        
    def getBlocks(self):
        return self.blocks
    
    #File is added to Folder Parent
    def createChildren(self,filesystem,filesystemParent):
        filesystemParent


#Best fits block Implementation    
class BestFitBlock:
    def __init__(self):
        self
 
    def getEmptyBlock(self,blocks,size):
        selectedBlock = blocks[0]
        for index,block in enumerate(blocks):
            if block.getSize() >= size:
                if block.getSize() <= selectedBlock.getSize():  
                    selectedBlock = block
                    indexBlock = index
        return blocks.pop(indexBlock)

#Worse fit block Implementation    
class WorseFitBlock:
    def __init__(self):
        self
 
    def getEmptyBlock(self,blocks,size):
        selectedBlock = blocks[0]
        for index,block in enumerate(blocks):
            if block.getSize() >= size:
                if block.getSize() >= selectedBlock.getSize():  
                    selectedBlock = block
                    indexBlock = index
        return blocks.pop(indexBlock) 


class FirstFitBlock:
    def __init__(self):
        self
 
    def getEmptyBlock(self,blocks,size):
        for index,block in enumerate(blocks):
            if block.getSize() >= size:
                indexBlock = index
                break
        return blocks.pop(indexBlock)
            